<template>
  <div class="render-form-container">
    <van-config-provider v-if="h5" :z-index="3000">
      <van-form
        ref="widgetFormRef"
        :class="[{ detail }]"
        :label-align="(jsonObject.formConfig && jsonObject.formConfig.h5?.labelPosition) || 'left'"
        :input-align="
          jsonObject.formConfig && jsonObject.formConfig.h5?.labelPosition === 'top'
            ? 'left'
            : jsonObject.formConfig.h5?.inputAlign || 'right'
        "
        :label-width="
          jsonObject.formConfig && jsonObject.formConfig.h5?.labelPosition === 'top'
            ? '100%'
            : (jsonObject.formConfig && jsonObject.formConfig.h5?.labelWidth) || '80px'
        "
        validate-first
        scroll-to-error
        show-error
      >
        <div>
          <template v-for="(widget, index) in widgetList" :key="index">
            <div v-if="widget.type === 'correlation' && widget.permission < 2">
              <component
                :is="`m-${widget.type}`"
                v-model="formModel[widget.options.name]"
                :form-data="formModel"
                :widget-list="jsonObject.widgetList"
                :item="widget"
                :index="index"
                :h5="true"
                :detail="detail"
              >
                <template v-if="$slots.correlation" #default="{ item, remove }">
                  <slot name="correlation" :item="item" :remove="remove" />
                </template>
                <template v-if="$slots.correlationDialog" #dialog="{ options, callback, save }">
                  <slot name="correlationDialog" :options="options" :callback="callback" :save="save" />
                </template>
              </component>
            </div>
            <component
              :is="`m-${widget.type}`"
              v-else-if="widget.permission < 2"
              v-model="formModel[widget.options.name]"
              :form-data="formModel"
              :widget-list="jsonObject.widgetList"
              :item="widget"
              :index="index"
              :h5="true"
              :detail="detail"
            />
          </template>
        </div>
      </van-form>
    </van-config-provider>

    <el-form
      v-else
      ref="widgetFormRef"
      class="ep-form-widget-list flex"
      :class="[{ detail }]"
      :model="formModel"
      :validate-on-rule-change="false"
      :hide-required-asterisk="false"
      :label-position="(jsonObject.formConfig && jsonObject.formConfig.pc?.labelPosition) || 'left'"
      :label-width="
        jsonObject.formConfig && jsonObject.formConfig.pc?.labelPosition === 'top'
          ? '100%'
          : (jsonObject.formConfig && jsonObject.formConfig.pc?.labelWidth) || '100px'
      "
      :size="(jsonObject.formConfig && jsonObject.formConfig.pc?.size) || 'default'"
    >
      <div v-for="(widget, index) in widgetList" :key="index" style="width: 100%">
        <div v-if="widget.type === 'correlation' && widget.permission < 2">
          <component
            :is="`m-${widget.type}`"
            v-model="formModel[widget.options.name]"
            :form-data="formModel"
            :widget-list="jsonObject.widgetList"
            :item="widget"
            :index="index"
            :detail="detail"
          >
            <template v-if="$slots.correlation" #default="{ item, remove }">
              <slot name="correlation" :item="item" :remove="remove" />
            </template>
            <template v-if="$slots.correlationDialog" #dialog="{ options, callback }">
              <slot name="correlationDialog" :options="options" :callback="callback" />
            </template>
          </component>
        </div>
        <component
          :is="`m-${widget.type}`"
          v-else-if="widget.permission < 2"
          v-model="formModel[widget.options.name]"
          :form-data="formModel"
          :widget-list="jsonObject.widgetList"
          :item="widget"
          :index="index"
          :detail="detail"
        />
      </div>
    </el-form>
  </div>
</template>

<script>
import _ from 'lodash'
import { ref, computed, watch } from 'vue'

export default {
  name: 'RenderForm',
  props: {
    /** 是否渲染H5 UI组件 */
    h5: {
      type: Boolean,
      default: false
    },
    /** Json表单配置 */
    jsonObject: {
      type: Object,
      required: true
    },
    /** 表单数据 */
    formData: {
      type: Object,
      default: null
    },
    /**
     * 组件权限 0可编辑 1只读 2隐藏
     * key value
     */
    permissions: {
      type: Object,
      default: null
    },
    /** 详情页中展示 */
    detail: {
      type: Boolean,
      default: false
    }
  },
  emits: ['change'],
  setup(props, { emit }) {
    const _filterWidget = (list, permissions) => {
      const widgetList = []
      if (list && Array.isArray(list)) {
        for (let index = 0; index < list.length; index++) {
          const item = _.cloneDeep(list[index])
          if (item.type === 'card' && item.components) {
            widgetList.push(..._filterWidget(item.components, permissions))
          } else {
            item.permission = 0 // 组件权限 0可编辑 1只读 2隐藏
            if (permissions && permissions[item.id] !== undefined) {
              item.permission = permissions[item.id]
            }
            widgetList.push(item)
          }
        }
      }
      return widgetList
    }
    const widgetList = computed(() => {
      const list = _filterWidget(props.jsonObject.widgetList, props.permissions)
      return list
    })

    const widgetFormRef = ref(null)

    /** 允许作为流程条件的组件 */
    const widgetTypes = ['input', 'textarea', 'number', 'radio', 'checkbox', 'select', 'computedays', 'amount']

    /** 表单数据对象 */
    const formModel = ref({})
    watch(
      () => props.formData,
      (val) => {
        if (val) {
          formModel.value = _.cloneDeep(val)
        }
      },
      { immediate: true }
    )

    /**
     * vue3监听对象时无法获取oldVal修改之前的值
     * 这里将监听对象序列化成字符串解决监听问题
     * 通过workflow字段只监听允许作为流程条件的组件
     * 如果使用了card组件，单独处理内部组件的值变化
     * 监听表单数据变化，触发change事件
     */
    watch(
      () => _.cloneDeep(formModel.value),
      (val, oldVal) => {
        const { widgetList } = props.jsonObject
        if (widgetList) {
          const filter = widgetList.filter((m) => widgetTypes.includes(m.type) && m.options && m.options.workflow)
          const names = filter.map((p) => p.options.name)
          const cardFilter = widgetList.filter((m) => m.type === 'card')
          if (cardFilter.length > 0) {
            const cardNames = cardFilter
              .map((c) => c.components)
              .flat()
              .filter((m) => widgetTypes.includes(m.type) && m.options && m.options.workflow)
              .map((c) => c.options.name)
            names.push(...cardNames)
          }

          const change = Object.keys(val).some((k) => names.includes(k) && val[k] !== oldVal[k])
          if (change) {
            emit('change', val)
          }
        }
      }
    )

    /** 获取表单数据 */
    const getFormModel = () => {
      return new Promise((resolve, reject) => {
        widgetFormRef.value
          .validate()
          .then(() => {
            resolve(formModel.value)
          })
          .catch((error) => {
            reject(error)
          })
      })
    }
    /** 清空表单数据 */
    const clearFormModel = () => {
      if (props.h5) {
        /** vant不会重置绑定数据 */
        formModel.value = _.cloneDeep(props.formData || {})
        widgetFormRef.value.resetValidation()
      } else {
        widgetFormRef.value.resetFields()
      }
    }

    return {
      widgetFormRef,
      formModel,
      widgetList,
      getFormModel,
      clearFormModel
    }
  }
}
</script>

<style lang="scss">
.render-form-container {
  height: 100%;

  .ep-form-widget-list {
    height: 100%;

    &.flex {
      display: flex;
      flex-direction: row;
      flex-wrap: wrap;
      align-content: flex-start;
    }
  }
  // 移动端支持上下布局方式
  .van-form {
    .van-field--label-top {
      flex-wrap: wrap;
    }
    .van-field__label--top {
      margin-bottom: var(--van-padding-xs);
    }
  }
  // vant 星号显示标签右侧
  .van-field__label--required:before {
    margin-right: 0 !important;
    content: '';
  }
  .van-field__label--required {
    label {
      position: relative;
      padding-right: 10px;
      &::before {
        content: '*';
        color: var(--van-field-required-mark-color);
        position: absolute;
        right: 0;
      }
    }
  }
}
</style>
